Skip to content

Fix client hanging on HTTP 4xx/5xx errors#2803

Merged
jlowin merged 1 commit intomainfrom
fix/client-http-error-propagation
Jan 7, 2026
Merged

Fix client hanging on HTTP 4xx/5xx errors#2803
jlowin merged 1 commit intomainfrom
fix/client-http-error-propagation

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Jan 7, 2026

When using HTTP transports, server errors (4xx/5xx) are raised in the background _session_runner task rather than in the code path awaiting a response. This caused methods like call_tool() to hang indefinitely waiting for a response that would never arrive.

The fix adds _await_with_session_monitoring() which uses asyncio.wait() to monitor both the pending call and the session task. If the session task fails, its exception is propagated immediately instead of hanging:

async with Client("http://server.example/mcp") as client:
    # Before: hangs forever if server returns 500
    # After: raises httpx.HTTPStatusError immediately
    result = await client.call_tool("my_tool", {})

All session-calling methods now use this monitoring, including call_tool, list_tools, read_resource, get_prompt, etc.

Closes #2595

When using HTTP transports, server errors are raised in the background
session runner task, not in the code path awaiting a response. This
caused calls like client.call_tool() to hang indefinitely.

Added _await_with_session_monitoring() to detect session task failures
and propagate exceptions immediately instead of hanging.

Closes #2595
@marvin-context-protocol marvin-context-protocol Bot added bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. client Related to the FastMCP client SDK or client-side functionality. http Related to HTTP transport, networking, or web server functionality. labels Jan 7, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 7, 2026

Walkthrough

A new internal utility method _await_with_session_monitoring is introduced in the Client class to wrap coroutines and monitor a background session task during execution. The method awaits coroutines directly if no session_task exists, re-raises session exceptions if the session_task has already completed, or races the provided coroutine against the session_task—canceling the coroutine and propagating session errors if the session_task completes first. This wrapper is applied across numerous MCP call paths including ping, set_logging_level, list_resources_mcp, list_prompts_mcp, complete_mcp, list_tools_mcp, call_tool_mcp, and others. Required imports (Coroutine, suppress) and a type variable (ResultT) are added to support the monitoring logic.

Possibly related PRs

  • PR #2591: Modifies session-task exception handling in src/fastmcp/client/client.py by preserving and re-raising session/McpError exceptions during connect and cleanup operations.
  • PR #2615: Updates session lifecycle management in src/fastmcp/client/client.py to centralize session state reset and improve cancellation cleanup for session_task and transport handling.
  • PR #2656: Updates docstrings of the same Client methods that this PR modifies (wrapping awaits with _await_with_session_monitoring) to add McpError documentation.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix client hanging on HTTP 4xx/5xx errors' clearly and concisely describes the main issue being resolved in this changeset.
Description check ✅ Passed The description comprehensively explains the problem (background task exceptions causing hangs), the solution (session monitoring utility), and provides a code example. It closes issue #2595 and covers the essential context.
Linked Issues check ✅ Passed The PR addresses all primary objectives from #2595: implements concurrent monitoring via _await_with_session_monitoring(), propagates session exceptions immediately, preserves existing behavior without session_task, and handles safe cancellation.
Out of Scope Changes check ✅ Passed All changes are focused on the session monitoring mechanism in client.py. The new utility and its integration across MCP call methods are directly scoped to the HTTP error propagation fix outlined in issue #2595.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10fb217 and 6e4feb4.

⛔ Files ignored due to path filters (1)
  • tests/client/test_client.py is excluded by none and included by none
📒 Files selected for processing (1)
  • src/fastmcp/client/client.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/client/client.py
🧠 Learnings (1)
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required

Applied to files:

  • src/fastmcp/client/client.py
🪛 Ruff (0.14.10)
src/fastmcp/client/client.py

692-692: Avoid specifying long messages outside the exception class

(TRY003)


713-713: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
🔇 Additional comments (16)
src/fastmcp/client/client.py (16)

9-10: LGTM!

The new imports (Coroutine, suppress) and the ResultT type variable are appropriate additions to support the session monitoring helper with proper type annotations.

Also applies to: 98-98


753-754: LGTM!

Simple and correct application of the session monitoring wrapper to ping() and set_logging_level().

Also applies to: 787-787


808-811: LGTM!

Consistent application of session monitoring to resource listing methods.

Also applies to: 841-844


894-904: LGTM!

Both the meta-aware path and the standard path are correctly wrapped with session monitoring.


997-1003: LGTM!

The task-based resource read correctly uses session monitoring.


1052-1053: LGTM!

Session monitoring correctly applied to list_prompts_mcp().


1117-1127: LGTM!

Both code paths in get_prompt_mcp() correctly use session monitoring.


1226-1232: LGTM!

Session monitoring correctly applied to _get_prompt_as_task().


1277-1282: LGTM!

Session monitoring correctly applied to complete_mcp().


1325-1326: LGTM!

Session monitoring correctly applied to list_tools_mcp().


1380-1389: LGTM!

This is the key fix for the reported issue. call_tool_mcp() now correctly propagates HTTP errors from the session task instead of hanging indefinitely.


1562-1568: LGTM!

Session monitoring correctly applied to _call_tool_as_task().


1604-1609: LGTM!

Session monitoring correctly applied to get_task_status() and get_task_result().

Also applies to: 1631-1639


1667-1672: LGTM!

Session monitoring correctly applied to list_tasks() and cancel_task().

Also applies to: 1707-1712


660-721: Well-designed session monitoring implementation.

The method correctly handles:

  • Direct await when no session task exists
  • Early failure detection if session task already completed
  • Racing the call against the session task with proper cleanup
  • External cancellation propagation

The use of anyio.CancelScope(shield=True) combined with suppress(asyncio.CancelledError) (lines 706-707, 719-720) ensures cleanup completes even during cancellation. This pattern is already established elsewhere in the file (lines 555-560) and is safe since anyio's cancel scope only affects the current task's cancellation state, not the awaited asyncio task.

Type annotations are complete, exception handling is specific (asyncio.CancelledError), and the code follows existing patterns in the codebase.


771-791: Notification methods are correctly implemented without session monitoring.

These fire-and-forget notification methods (cancel(), progress(), send_roots_list_changed()) intentionally omit the session monitoring wrapper because they don't await responses. Session monitoring is needed only for request methods that wait for responses while the session task might fail (e.g., send_ping(), set_logging_level()). Notifications queue immediately and return without blocking, so they cannot hang on session task failures.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jlowin jlowin merged commit 517a860 into main Jan 7, 2026
13 checks passed
@jlowin jlowin deleted the fix/client-http-error-propagation branch January 7, 2026 14:50
tonyxwz pushed a commit to tonyxwz/fastmcp that referenced this pull request Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. client Related to the FastMCP client SDK or client-side functionality. http Related to HTTP transport, networking, or web server functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Client hangs indefinitely when MCP server returns HTTP 4xx/5xx errors instead of raising exception

1 participant